package ru.ganyaev.workstation_vm_manager;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import ru.ganyaev.external_process.BasicExternalProcess;
import ru.ganyaev.external_process.Command;
import ru.ganyaev.external_process.ExecuteExternallProcessException;
import ru.ganyaev.external_process.ExternalProcess;
import ru.ganyaev.external_process.ExternalProcessResult;
import ru.ganyaev.file_system.BasicFileSystemManager;
import ru.ganyaev.file_system.FileSystemManager;
import ru.ganyaev.file_system.FileSystemManagerException;
import ru.ganyaev.utils.NameGenerator;
import ru.ganyaev.utils.ScriptGenerator;
import ru.ganyaev.utils.UuidNameGenerator;
import ru.ganyaev.utils.WindowsScriptGenerator;
import ru.ganyaev.vm_manager.VirtualMachine;
import ru.ganyaev.vmrun.PowerOptions;
import ru.ganyaev.vmrun.UserInterfaces;
import ru.ganyaev.vmrun.VmRunCommands;

public class WorkstationVirtualMachine extends VirtualMachine {
    private static final Logger LOGGER = LogManager.getRootLogger();
    
    private String name;
    private VmRunCommands vmRunCommands;
    private String vmxPath;
    private String guestUser;
    private String guestPassword;
    private Path tempFolderPath;

    private ExternalProcess externalProcess = new BasicExternalProcess();
    private FileSystemManager fileSystemManager = new BasicFileSystemManager();

    private NameGenerator nameGenerator = new UuidNameGenerator();
    private ScriptGenerator scriptGenerator = new WindowsScriptGenerator();

    WorkstationVirtualMachine(String name, VmRunCommands vmRunCommands,
            String vmxPath, String guestUser, String guestPassword,
            Path tempFolderPath) {
        this.name = name;
        this.vmRunCommands = vmRunCommands;
        this.vmxPath = vmxPath;
        this.guestUser = guestUser;
        this.guestPassword = guestPassword;
        this.tempFolderPath = tempFolderPath;
    }

    WorkstationVirtualMachine(String name, VmRunCommands vmRunCommands,
            String vmxPath, String guestUser, String guestPassword,
            Path tempFolderPath, ExternalProcess externalProcess,
            FileSystemManager fileSystemManager) {
        this(name, vmRunCommands, vmxPath, guestUser, guestPassword,
                tempFolderPath);
        this.externalProcess = externalProcess;
        this.fileSystemManager = fileSystemManager;
    }

    @Override
    public void start() throws WorkstationVirtualMachineException {
        Command startVmCommand = vmRunCommands.getStartCommand(vmxPath,
                UserInterfaces.GUI);
        LOGGER.debug("Try to execute start vm command: " + startVmCommand);
        try {
            externalProcess.executeAndCheckExitCode(startVmCommand);
        } catch (ExecuteExternallProcessException e) {
            throw new WorkstationVirtualMachineException(
                    "Can not execute start vm command.", e);
        }
        Command listProcesesCommand = vmRunCommands
                .getListProcessesInGuestCommand(vmxPath, guestUser,
                        guestPassword);
        LOGGER.debug("Try to execute list processes command: "
                + listProcesesCommand);
        try {
            externalProcess.executeAndCheckExitCode(listProcesesCommand);
        } catch (ExecuteExternallProcessException e) {
            throw new WorkstationVirtualMachineException(
                    "Can not execute list proceses command.", e);
        }
    }

    @Override
    public void stop() throws WorkstationVirtualMachineException {
        Command stopVmCommand = vmRunCommands.getStopCommand(vmxPath,
                PowerOptions.SOFT);
        LOGGER.debug("Try to execute stop vm command: " + stopVmCommand);
        try {
            externalProcess.executeAndCheckExitCode(stopVmCommand);
        } catch (ExecuteExternallProcessException e) {
            throw new WorkstationVirtualMachineException(
                    "Can not execute stop vm command.", e);
        }
    }

    @Override
    public void reset() throws WorkstationVirtualMachineException {
        Command resetVmCommand = vmRunCommands.getResetCommand(vmxPath,
                PowerOptions.SOFT);
        LOGGER.debug("Try to execute reset vm command: " + resetVmCommand);
        try {
            externalProcess.executeAndCheckExitCode(resetVmCommand);
        } catch (ExecuteExternallProcessException e) {
            throw new WorkstationVirtualMachineException(
                    "Can not execute reset vm command.", e);
        }
        Command listProcesesCommand = vmRunCommands
                .getListProcessesInGuestCommand(vmxPath, guestUser,
                        guestPassword);
        LOGGER.debug("Try to execute list processes command: "
                + listProcesesCommand);
        try {
            externalProcess.executeAndCheckExitCode(listProcesesCommand);
        } catch (ExecuteExternallProcessException e) {
            throw new WorkstationVirtualMachineException(
                    "Can not execute list proceses command.", e);
        }
    }

    @Override
    public void copyFileToGuest(String hostPath, String guestPath)
            throws WorkstationVirtualMachineException {
        try {
            if (!fileSystemManager.isPathExists(Paths.get(hostPath))) {
                throw new WorkstationVirtualMachineException(
                        "Can not find host path '" + hostPath + "'.");
            }
        } catch (FileSystemManagerException e) {
            throw new WorkstationVirtualMachineException(
                    "Can not check hostPath: " + hostPath);
        }

        Command copyFileCommand = vmRunCommands
                .getCopyFileFromHostToGuestCommand(vmxPath, hostPath,
                        guestPath, guestUser, guestPassword);

        LOGGER.debug("Try to execute copy file from host to guest command: "
                + copyFileCommand);
        try {
            externalProcess.executeAndCheckExitCode(copyFileCommand);
        } catch (ExecuteExternallProcessException e) {
            throw new WorkstationVirtualMachineException(
                    "Can not execute copy file from host to guest command.", e);
        }
    }

    @Override
    public void copyFileFromGuest(String guestPath, String hostPath)
            throws WorkstationVirtualMachineException {
        Command copyFileCommand = vmRunCommands
                .getCopyFileFromGuestToHostCommand(vmxPath, hostPath,
                        guestPath, guestUser, guestPassword);

        LOGGER.debug("Try to execute copy file from guest to host command: "
                + copyFileCommand);
        try {
            externalProcess.executeAndCheckExitCode(copyFileCommand);
        } catch (ExecuteExternallProcessException e) {
            throw new WorkstationVirtualMachineException(
                    "Can not execute copy file from guest to host command.", e);
        }
    }

    @Override
    public ExternalProcessResult runProcess(String processName,
            List<String> arguments) throws WorkstationVirtualMachineException {
        String procTempFolderName = nameGenerator.generateUnicName();
        Path procTempHostPath = tempFolderPath.resolve(procTempFolderName);
        Path procTempGuestPath = getRootPathOnGuest().resolve(
                procTempFolderName);

        LOGGER.debug("Try to create process temp folder '" + procTempHostPath
                + "' on host.");
        try {
            fileSystemManager.createFolder(procTempHostPath);
        } catch (FileSystemManagerException e) {
            throw new WorkstationVirtualMachineException(
                    "Can not create process temp folder ('" + procTempHostPath
                            + "') on host machine.", e);
        }
        LOGGER.debug("Try to create process temp folder '" + procTempGuestPath
                + "' on guest.");
        Command createFolderCommand = vmRunCommands
                .getCreateDirectoryInGuestCommand(vmxPath,
                        procTempGuestPath.toString(), guestUser, guestPassword);
        try {
            externalProcess.executeAndCheckExitCode(createFolderCommand);
        } catch (ExecuteExternallProcessException e) {
            throw new WorkstationVirtualMachineException(
                    "Can not execute create process temp folder('"
                            + procTempGuestPath + "')  command: "
                            + createFolderCommand, e);
        }

        Command runScriptCommand = vmRunCommands.getRunScriptInGuestCommand(
                vmxPath, getExecutorPath(), scriptGenerator.generateScriptText(
                        processName, arguments, procTempGuestPath), guestUser,
                guestPassword, false);
        LOGGER.debug("Try to execute run script command: " + runScriptCommand);
        ExternalProcessResult runScriptResult = null;
        try {
            runScriptResult = externalProcess.execute(runScriptCommand);
        } catch (ExecuteExternallProcessException e) {
            throw new WorkstationVirtualMachineException(
                    "Can not execute run script command: " + runScriptCommand,
                    e);
        }
        LOGGER.debug("Result of run script in guest: " + runScriptResult);

        Path stdOutGuest = procTempGuestPath.resolve(scriptGenerator
                .getStdOutFileName());
        Path stdOutHost = procTempHostPath.resolve(scriptGenerator
                .getStdOutFileName());

        Path stdErrorGuest = procTempGuestPath.resolve(scriptGenerator
                .getStdErrorFileName());
        Path stdErrorHost = procTempHostPath.resolve(scriptGenerator
                .getStdErrorFileName());

        Command copyStdOutFromGuestCommand = vmRunCommands
                .getCopyFileFromGuestToHostCommand(vmxPath,
                        stdOutGuest.toString(), stdOutHost.toString(),
                        guestUser, guestPassword);
        Command copyStdErrorFromGuestCommand = vmRunCommands
                .getCopyFileFromGuestToHostCommand(vmxPath,
                        stdErrorGuest.toString(), stdErrorHost.toString(),
                        guestUser, guestPassword);

        LOGGER.debug("Try to execute copy stdout and stderror files commands: "
                + copyStdOutFromGuestCommand + ", "
                + copyStdErrorFromGuestCommand);
        try {
            externalProcess.executeAndCheckExitCode(copyStdOutFromGuestCommand);
            externalProcess
                    .executeAndCheckExitCode(copyStdErrorFromGuestCommand);
        } catch (ExecuteExternallProcessException e) {
            throw new WorkstationVirtualMachineException(
                    "Can not copy stdout and stderror files from guest.", e);
        }

        ExternalProcessResult processResult = null;
        try {
            processResult = new WorkstationExternalProcessResult(
                    runScriptResult, stdOutHost, stdErrorHost);
        } catch (WorkstationExternalProcessResultException e) {
            LOGGER.error("Can not create workstation external process result.",
                    e);
            processResult = runScriptResult;
        }

        return processResult;
    }

    @Override
    public void runProcessImmediately(String processName, List<String> arguments)
            throws WorkstationVirtualMachineException {
        Command runProcessCommand = vmRunCommands
                .getRunProgramInGuestCommand(vmxPath, processName, arguments,
                        guestUser, guestPassword, true);

        LOGGER.debug("Try to execute run process immediately command: "
                + runProcessCommand);
        try {
            externalProcess.executeAndCheckExitCode(runProcessCommand);
        } catch (ExecuteExternallProcessException e) {
            throw new WorkstationVirtualMachineException(
                    "Can not execute run process immediately command.", e);
        }
    }

    String getVmxPath() {
        return vmxPath;
    }

    String getGuestUser() {
        return guestUser;
    }

    String getGuestPassword() {
        return guestPassword;
    }

    @Override
    public String getName() {
        return name;
    }

    // Эти два метода созданы как заготовка для разделения между win и linux
    // операционными гостевыми системами
    public Path getRootPathOnGuest() {
        return Paths.get("C:\\");
    }

    public String getExecutorPath() {
        return "\"\"";
    }
}
